#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#define TAG "screen"
#include "main.h"
#include "esp_lcd_panel_ops.h"
#include "driver/i2c_master.h"
#include "esp_timer.h"
#include "lvgl.h"
#include "lv_demos.h"
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>

#define DRAW_BUF_LINES 24 // number of display lines in each draw buffer (1/10 of screen height)
#define TICK_PERIOD_MS 2
#define TASK_MAX_DELAY_MS 500
#define TASK_MIN_DELAY_MS 1
#define TASK_STACK_SIZE (8 * 1024)
#define TASK_PRIORITY 2

#define MY_DISP_HOR_RES 320
#define MY_DISP_VER_RES 240

static _lock_t lvgl_api_lock;
esp_lcd_touch_handle_t ret_touch;
extern esp_lcd_panel_handle_t panel;
extern esp_lcd_panel_io_handle_t io;
esp_err_t touch_init(esp_lcd_touch_handle_t *ret_touch)
{
    i2c_master_bus_handle_t i2c_handle;
    i2c_master_get_bus_handle(CONFIG_I2C_PORT, &i2c_handle);
    /* Initialize touch */
    const esp_lcd_touch_config_t tp_cfg = {
        .x_max = MY_DISP_VER_RES,
        .y_max = MY_DISP_HOR_RES,
        .rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset
        .int_gpio_num = GPIO_NUM_NC,
        .levels = {
            .reset = 0,
            .interrupt = 0,
        },
        .flags = {
            .swap_xy = 1,
            .mirror_x = 1,
            .mirror_y = 0,
        },
    };
    esp_lcd_panel_io_handle_t tp_io_handle = NULL;
    esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG();
    tp_io_config.scl_speed_hz = CONFIG_I2C_SPEED_KHZ * 1000;
    esp_lcd_new_panel_io_i2c(i2c_handle, &tp_io_config, &tp_io_handle);
    return esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, ret_touch);
}

void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
    uint16_t touchpad_x[1] = {0};
    uint16_t touchpad_y[1] = {0};
    uint8_t touchpad_cnt = 0;

    esp_lcd_touch_handle_t touch_pad = lv_indev_get_user_data(indev);
    esp_lcd_touch_read_data(touch_pad);
    /* Get coordinates */
    bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_pad, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1);

    if (touchpad_pressed && touchpad_cnt > 0)
    {
        data->point.x = touchpad_x[0];
        data->point.y = touchpad_y[0];
        data->state = LV_INDEV_STATE_PRESSED;
    }
    else
    {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}

void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    // example_lvgl_port_update_callback(disp);
    esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    // because SPI LCD is big-endian, we need to swap the RGB bytes order
    lv_draw_sw_rgb565_swap(px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1));
    // copy a buffer's content to a specific area of the display
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}

void lvgl_tick(void *arg)
{
    /* Tell LVGL how many milliseconds has elapsed */
    lv_tick_inc(TICK_PERIOD_MS);
}
bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp);
    return false;
}
void screent_task(void *arg)
{
    lv_init();
    touch_init(&ret_touch);
    lv_display_t *display = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);

    size_t draw_buffer_sz = MY_DISP_HOR_RES * DRAW_BUF_LINES * sizeof(lv_color16_t);

    void *buf1 = spi_bus_dma_memory_alloc(SPI3_HOST, draw_buffer_sz, 0);
    assert(buf1);
    void *buf2 = spi_bus_dma_memory_alloc(SPI3_HOST, draw_buffer_sz, 0);
    assert(buf2);

    // initialize LVGL draw buffers
    lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
    // associate the mipi panel handle to the display
    lv_display_set_user_data(display, panel);
    // set color depth
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
    // set the callback which can copy the rendered image to an area of the display
    lv_display_set_flush_cb(display, example_lvgl_flush_cb);

    lv_indev_t *indev_touchpad = lv_indev_create();
    lv_indev_set_display(indev_touchpad, display);
    lv_indev_set_type(indev_touchpad, LV_INDEV_TYPE_POINTER);
    lv_indev_set_user_data(indev_touchpad, ret_touch);
    lv_indev_set_read_cb(indev_touchpad, touchpad_read);

    ESP_LOGI(TAG, "Install LVGL tick timer");
    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &lvgl_tick,
        .name = "lvgl_tick"};
    esp_timer_handle_t lvgl_tick_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, TICK_PERIOD_MS * 1000));
    ESP_LOGI("TAG", "Register io panel event callback for LVGL flush ready notification");
    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = notify_lvgl_flush_ready,
    };
    /* Register done callback */
    ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io, &cbs, display));
    ESP_LOGI(TAG, "Create LVGL task");

    // Lock the mutex due to the LVGL APIs are not thread-safe
    _lock_acquire(&lvgl_api_lock);
    // 检查文件系统
    create_file_manager(lv_scr_act());
    _lock_release(&lvgl_api_lock);

    ESP_LOGI(TAG, "Starting LVGL task");
    int32_t time_till_next_ms = 0;
    int32_t time_threshold_ms = 1000 / CONFIG_FREERTOS_HZ;
    while (1)
    {
        _lock_acquire(&lvgl_api_lock);
        time_till_next_ms = lv_timer_handler();
        _lock_release(&lvgl_api_lock);
        // Ensure minimum delay to prevent watchdog timeout
        time_till_next_ms = MAX(time_till_next_ms, time_threshold_ms);
        vTaskDelay(pdMS_TO_TICKS(time_till_next_ms));
    }
}

void app_screen_init(void)
{
    xTaskCreate(screent_task, "screent_task", TASK_STACK_SIZE, NULL, TASK_PRIORITY, NULL);
}
